// ========================================================================
// Copyright (c) 2022 ChangYou, All rights reserved.
// http://www.changyou.com/
// 
// Filename:    RenderingQueue.cs
// Time:        2022-08-22 21:09:43
// Author:      xiangjinbao
// Email:       xiangjinbao@cyou-inc.com
// Version:     v2022.0.1
// Description: RenderingQueue
// ========================================================================

using System.Collections.Generic;
using UnityEngine;

namespace ProjectTW.Foliage
{
    /// <summary>
    /// 一个渲染队列.
    /// </summary>
    public class RenderingQueue
    {
        private int RenderingInstancesPoolCount(FoliageInstanceObject instance, FoliagePrototype prototype)
        {
            int meshInstancesAmount = 0;
            FoliageGPUMesh foliageGpuMesh;

            if (prototype.Enabled)
            {
                foliageGpuMesh = instance.MeshInstances[prototype.Id];
                meshInstancesAmount += foliageGpuMesh.MeshInstancesGroup.MeshInstanceArray.Length;
            }

            meshInstancesAmount *= 9; // multiply by the possible amount of manager instances.

            return meshInstancesAmount;
        }

        public const int GENERATION_RADIUS = 3;
        public const int GENERATION_RADIUS_OFFSET = 1;

        public FoliageInstanceObject FoliageInstance;
        public Dictionary<int, RenderingQueueInstance> RenderInstances;
        public bool QueueInstanceNull = true;

        public RenderingQueue(FoliageInstanceObject foliageInstance, FoliageChunk chunk, Vector3 snapPosition, Dictionary<int, RenderingQueueInstance> fetchedRenderInstances)
        {
            FoliageInstance = foliageInstance;
            RenderInstances = fetchedRenderInstances;
            // 构建RenderQueueInstance
            GenerateQueueInstance(snapPosition, foliageInstance, chunk);
        }

        internal void UpdateDensities()
        {
            List<FoliagePrototype> prototypes = FoliageDB.UnSortedPrototypes;
            FoliagePrototype prototype;
            int mapWidth = FoliageInstance.FoliageAreaResolutionIntegral;
            float densityMultiplier = FoliageManager.Instance.Density;
            RenderingQueueInstance renderingQueueInstance;

            for (int i = 0; i < prototypes.Count; i++)
            {
                prototype = prototypes[i];

                if (!prototype.Enabled || RenderInstances == null)
                {
                    continue;
                }

                renderingQueueInstance = RenderInstances[prototype.Id];

                if (renderingQueueInstance != null)
                {
                    renderingQueueInstance.UpdateDensities(mapWidth, densityMultiplier);
                }
            }
        }

        internal void Render(Camera camera, Plane[] cameraPlanes)
        {
            UpdateMaterialBlock();
            List<FoliagePrototype> prototypes = FoliageDB.UnSortedPrototypes;
            RenderingQueueInstance currentQueueInstance;
            FoliagePrototype prototype;

            if (FoliageInstance == null || camera == null || RenderInstances == null)
            {
                return;
            }

            Vector3 normalizedCameraPosition = camera.transform.position - FoliageInstance.Pos;
            normalizedCameraPosition.x = Mathf.Clamp(normalizedCameraPosition.x, 0, FoliageManager.Foliage_Instance_Area_Size - 1);
            normalizedCameraPosition.y = camera.transform.position.y;
            normalizedCameraPosition.z = Mathf.Clamp(normalizedCameraPosition.z, 0, FoliageManager.Foliage_Instance_Area_Size - 1);

            for (int i = 0; i < prototypes.Count; i++)
            {
                prototype = prototypes[i];

                if (!prototype.Enabled)
                {
                    continue;
                }

                currentQueueInstance = RenderInstances[prototype.Id];

                if (currentQueueInstance == null)
                {
                    continue;
                }

                currentQueueInstance.Render(RenderingPipielineCore.PropertyBlock, camera, cameraPlanes, normalizedCameraPosition, prototype.UseInstancing);
            }
        }

        internal void Destroy()
        {
            FoliageInstance = null;
            List<FoliagePrototype> prototypes = FoliageDB.UnSortedPrototypes;
            RenderingQueueInstance currentQueueInstance;
            FoliagePrototype prototype;

            for (int i = 0; i < prototypes.Count; i++)
            {
                prototype = prototypes[i];

                if (!prototype.Enabled)
                {
                    continue;
                }

                currentQueueInstance = RenderInstances[prototype.Id];

                if (currentQueueInstance == null)
                {
                    continue;
                }

                currentQueueInstance.SimulatedMeshInstances.Clear();
            }

            RenderInstances = null;
        }

        private void UpdateMaterialBlock()
        {
            FoliageInstance.UpdateMaterialBlock(RenderingPipielineCore.PropertyBlock);
        }

        private void GenerateQueueInstance(Vector3 snapPosition, FoliageInstanceObject foliageInstance, FoliageChunk chunk)
        {
            Vector3 normalizedPosition = snapPosition;
            snapPosition -= foliageInstance.Pos;
            snapPosition.y = 0;
            List<FoliagePrototype> prototypes = FoliageDB.Instance.m_Prototypes;
            Vector3 instancePosition = new Vector3();
            FoliageDensityMap densityMap = foliageInstance.DensityMap;

            for (var i = 0; i < prototypes.Count; i++)
            {
                FoliagePrototype prototype = prototypes[i];
                UNFastList<RenderingQueueMeshInstanceSimulator> renderingQueueInstances = null;

                if (!prototype.Enabled || !chunk.InBounds(normalizedPosition, prototype.FadeDistance * RenderingPipielineCore.Instances_Offset_Multipler))
                {
                    continue; // safe distanc
                }

                FoliageGPUMesh foliageGpuMesh = foliageInstance.MeshInstances[prototype.Id];
                int gpuMeshLodsCount = foliageGpuMesh.MeshInstancesGroup.Count;
                int fadeDistance = (prototype.FadeDistance + FoliageMeshInstance.Generation_Safe_Distance) * 2;
                Vector3 relativeSnapPoint = snapPosition;
                relativeSnapPoint.x -= fadeDistance / 2f;
                relativeSnapPoint.z -= fadeDistance / 2f;

                for (int index = 0; index < gpuMeshLodsCount; index++)
                {
                    FoliageMeshInstance meshInstance = foliageGpuMesh.MeshInstancesGroup.MeshInstanceArray[index];
                    instancePosition.x = meshInstance.Position.x + relativeSnapPoint.x;
                    instancePosition.z = meshInstance.Position.z + relativeSnapPoint.z;

                    if ((instancePosition.x + meshInstance.BoundsSizeX < 0 || instancePosition.z + meshInstance.BoundsSizeZ < 0) ||
                        (instancePosition.x > FoliageManager.Foliage_Instance_Area_Size || instancePosition.z > FoliageManager.Foliage_Instance_Area_Size))
                    {
                        continue;
                    }

                    if (renderingQueueInstances == null)
                    {
                        renderingQueueInstances = RenderingPipielineCore.FastListPool.Count > 0 ? RenderingPipielineCore.FastListPool.Pop() : null;

                        if (renderingQueueInstances == null)
                        {
                            renderingQueueInstances = new UNFastList<RenderingQueueMeshInstanceSimulator>(RenderingInstancesPoolCount(foliageInstance, prototype));
                        }
                    }

                    renderingQueueInstances.Add(new RenderingQueueMeshInstanceSimulator(instancePosition, meshInstance, foliageInstance, prototype, snapPosition));
                }

                if (renderingQueueInstances == null || renderingQueueInstances.Count == 0)
                {
                    continue;
                }

                RenderingQueueInstance pipelineInstance = new RenderingQueueInstance(foliageInstance, prototype, foliageGpuMesh, densityMap)
                {
                    SimulatedMeshInstances = renderingQueueInstances
                };

                RenderInstances[prototype.m_FoliageIdInternal] = pipelineInstance;
                QueueInstanceNull = false;
            }

            if (RenderInstances.Count == 0)
            {
                RenderInstances = null;
            }
        }
    }
}
